using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace NEmu
{
    [Serializable]
    public abstract class Memory : IDisposable
    {
        private ulong m_MemBound;
        private Dictionary<ulong, MemoryPage> m_Pages;
        private MemoryPage m_CurrentPage;
        private ulong m_CurrentAddressOffset = 0;
        private ulong m_CurrentAddressBound = 0;
        private VirtualMachine m_ParentMachine;
        private bool disposed;

        public Memory(VirtualMachine machine)
        {
            m_ParentMachine = machine;
            m_MemBound = 0;
            m_CurrentAddressOffset = 0;
            m_CurrentAddressBound = 0;
            m_Pages = new Dictionary<ulong, MemoryPage>();
            m_CurrentPage = null;
            AllocatePages();
        }

        ~Memory()
        {
            Dispose(false);
        }

        protected abstract void AllocatePages();

        public virtual void Write(ulong address, byte value)
        {
            lock (this)
            {
                FindPage(address).Write(address - m_CurrentAddressOffset, value);
            }
        }

        public virtual byte Read(ulong address)
        {
            lock (this)
            {
                return FindPage(address).Read(address - m_CurrentAddressOffset);
            }
        }

        public void Reset()
        {
            foreach (MemoryPage page in m_Pages.Values)
            {
                page.Clear();
            }
        }

        public virtual ulong Size
        {
            get { return m_MemBound; }
        }

        public virtual byte this[ulong index]
        {
            get
            {
                return Read(index);
            }
            set
            {
                Write(index, value);
            }
        }

        public MemoryPage FindPage(ulong address)
        {
            // If the address isn't out of bounds, use the same page
            if (address >= m_CurrentAddressOffset && address < m_CurrentAddressBound)
            {
                return m_CurrentPage;
            }

            // Else we need to figure out the new page to access
            MemoryPage page = null;

            // Check each offset address and see where address falls in range with
            foreach (ulong offset in m_Pages.Keys)
            {
                // if address is >= of that key, it is a possible page
                if (address >= offset)
                {
                    // Get the reference of the page
                    if (m_Pages.TryGetValue(offset, out page))
                    {
                        // if the address within bounds of the page, then get it, else keep searching
                        if (address < (offset + page.Size))
                        {
                            m_CurrentPage = page;
                            m_CurrentAddressBound = offset + page.Size;
                            m_CurrentAddressOffset = offset;
                            return page;
                        }
                    }
                }
            }

            return page;
        }

        protected void AddPage(MemoryPage page)
        {
            m_Pages.Add(m_MemBound, page);
            m_MemBound += (page.Size + 1);
        }

        protected void AddManualPage(ulong address, MemoryPage page)
        {
            m_Pages.Add(address, page);
        }

        protected void ClearPages()
        {
            m_Pages.Clear();
        }

        public VirtualMachine ParentMachine
        {
            get { return this.m_ParentMachine; }
        }

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    foreach (MemoryPage page in m_Pages.Values)
                    {
                        page.Dispose();
                    }
                }

                disposed = true;
            }
        }

        #endregion
    }
}
